package org.ns.framework.datasource.mybatis;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.LinkedList;
import java.util.Random;
import java.util.function.BiFunction;
import java.util.function.Predicate;

import org.apache.ibatis.mapping.Environment;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;
import org.ns.framework.datasource.anns.DBSource;
import org.ns.framework.datasource.utils.DBSourceUtils;
import org.springframework.aop.support.AopUtils;
import org.springframework.core.annotation.AnnotatedElementUtils;

import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;

/**
 * Mybatis 引用测试, 方便开发测试
 * 
 * @author zhangxingyu
 *
 */
public class MybatisUseTest implements AutoCloseable {
	private SqlSessionFactory sqlSessionFactory;
	private String dataSourceName = "MybatisUseDemo" + new Random().nextInt(10000);
	private HikariDataSource dataSource;
	public static MybatisMapperManage mapperManage = new MybatisMapperManage();

	public MybatisUseTest(String url, String user, String password) {
		this(url, user, password, (a, b) -> {
			Environment environment = new Environment(a, new JdbcTransactionFactory(), b);
			Configuration configuration = new Configuration(environment);
			SqlSessionFactoryBuilder factoryBuilder = new SqlSessionFactoryBuilder();
			return factoryBuilder.build(configuration);
		});

	}

	public MybatisUseTest(String url, String user, String password,
			BiFunction<String, HikariDataSource, SqlSessionFactory> createSqlSessionFactory) {
		HikariConfig config = new HikariConfig();
		config.setJdbcUrl(url);
		config.setUsername(user);
		config.setPassword(password);
		config.setAutoCommit(false);
		config.setMaximumPoolSize(10);
		config.setMinimumIdle(2);
		dataSource = new HikariDataSource(config);
		sqlSessionFactory = createSqlSessionFactory.apply(dataSourceName, dataSource);
		mapperManage.setSqlSessionFactory(dataSourceName, sqlSessionFactory, false);
	}

	public <T> T getMapper(Class<T> mapperType) {
		if (dataSource.isClosed()) {
			return null;
		}
		return (T) mapperManage.getMapper(dataSourceName, mapperType);
	}

	public Connection getConnection() throws SQLException {
		return dataSource.getConnection();
	}
	
	public void close() {
		dataSource.close();
	}

	public String getDataSourceName() {
		return dataSourceName;
	}
	
	public static MybatisUseTest mysql(String host, String db, String user, String password,BiFunction<String, HikariDataSource, SqlSessionFactory> createSqlSessionFactory) {
		String url = "jdbc:mysql://%s/%s?useunicode=true&characterEncoding=utf8&useSSL=false&autoReconnect=true&serverTimezone=Asia/Shanghai";
		url = String.format(url, host, db);
		return new MybatisUseTest(url, user, password, createSqlSessionFactory);
	}
	public static MybatisUseTest mysql(String host, String db, String user, String password) {
		String url = "jdbc:mysql://%s/%s?useunicode=true&characterEncoding=utf8&useSSL=false&autoReconnect=true&serverTimezone=Asia/Shanghai";
		url = String.format(url, host, db);
		return new MybatisUseTest(url, user, password);
	}
	
	public void injector(String dataSourceName, Object bean) {
		injector(f -> {
			DBSource dbSource = AnnotatedElementUtils.getMergedAnnotation(f, DBSource.class);
			String dbname = DBSourceUtils.toSourceName(dbSource.value());
			return dataSourceName.equals(dbname);
		}, bean);
	}
	
	public void injector(Class<? extends Annotation> dataSource, Object bean) {
		injector(f -> {
			return f.isAnnotationPresent(dataSource);
		}, bean);
	}
	
	public void injector(Predicate<Field> test, Object bean) {
		Class<?> beanType = AopUtils.getTargetClass(bean);
		
		LinkedList<Class<?>> nexts = new LinkedList<Class<?>>();
		nexts.add(beanType);
		while (!nexts.isEmpty()) {
			Class<?> curr =	nexts.removeFirst();
			Class<?> next = curr.getSuperclass();
			if (next != null && next != Object.class) {
				nexts.add(next);
			}
			Field[] fields = curr.getDeclaredFields();
			for (Field field : fields) {
				Class<?> type = field.getType();
				if (type.isInterface() && test.test(field)) {
					try {
						field.setAccessible(true);
						Object mapper = getMapper(type);
						if (mapper != null) {
							field.set(bean, mapper);
						}
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
			}
			
		}
		
	}
}
